<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
  import platform, { IntlString, PlatformError } from '@hcengineering/platform'
  import ui, { Button, EditBox, IconClose, Label, IconError } from '@hcengineering/ui'
  import { createEventDispatcher } from 'svelte'
  import { type Integration } from '@hcengineering/account-client'
  import { isValidPhoneNumber } from 'libphonenumber-js'
  import { Analytics } from '@hcengineering/analytics'

  import PhoneInput from './PhoneInput.svelte'
  import PinPad from './PinPad.svelte'
  import telegram from '../plugin'
  import { command, getState, type IntegrationState, connect, disconnect } from '../api'

  export let integration: Integration | undefined = undefined
  export let reconnect: boolean = false

  let phone: string = ''
  let code: string = ''
  let password: string = ''
  let error: string = ''
  let isLoading: boolean = false

  const dispatch = createEventDispatcher()

  function close (value?: string): void {
    const connected = state.mode === 'Authorized' || state.mode === 'Configured'
    dispatch('close', { value, connected })
  }

  // Wrapper for command API with loading state management
  async function commandWithLoading (phone: string, action: 'start' | 'next', data?: string): Promise<IntegrationState> {
    if (isLoading) {
      throw new Error('Already processing request')
    }

    try {
      isLoading = true
      return await command(phone, action, data)
    } finally {
      isLoading = false
    }
  }

  interface UIState {
    mode: 'Loading' | 'WantPhone' | 'WantCode' | 'WantPassword' | 'Authorized' | 'Configured' | 'Unauthorized' | 'Error'
    hint?: string
    errorLabel?: IntlString

    buttons?: {
      primary?: { label: IntlString, handler?: () => any, disabled?: boolean }
      secondary?: { label: IntlString, handler?: () => any }
    }
  }

  let integrationState: IntegrationState = 'Loading'
  let state: UIState = { mode: 'Loading' }

  function h (handler: () => Promise<IntegrationState>) {
    return () => {
      handler()
        .then((i) => {
          integrationState = i
        })
        .catch((error: any) => {
          state = {
            mode: 'Error',
            hint: error.message,
            errorLabel: getErrorLabel(error),
            buttons: {
              primary: { label: ui.string.Ok, handler: close }
            }
          }
        })
    }
  }

  function getErrorLabel (error: any): IntlString | undefined {
    if (error instanceof PlatformError) {
      if (error.status.code === platform.status.Unauthorized || error.status.code === platform.status.Forbidden) {
        return telegram.string.IncorrectPhoneOrCode
      } else if (error.status.code === platform.status.ConnectionClosed) {
        return telegram.string.ServiceIsUnavailable
      }
    }
    const errorMessage: string = error.message ?? ''
    if (errorMessage.toLowerCase().includes('failed to fetch')) {
      return telegram.string.ServiceIsUnavailable
    }
    return telegram.string.UnknownError
  }

  $: {
    if (integrationState === 'Loading') {
      state = { mode: 'Loading' }
    } else if (integrationState === 'Missing') {
      state = {
        mode: 'WantPhone',
        buttons: {
          primary: {
            label: ui.string.Next,
            handler: h(() => commandWithLoading(phone, 'start')),
            disabled: !isValidPhoneNumber(phone) || isLoading
          },
          secondary: { label: telegram.string.Cancel, handler: close }
        }
      }
    } else {
      switch (integrationState.status) {
        case 'authorized': {
          const number = integrationState.number
          state = {
            mode: 'Authorized',
            hint: number,
            buttons: {
              primary: {
                label: ui.string.Ok,
                handler: () => {
                  close(number)
                }
              }
              // secondary: { label: telegram.string.Disconnect }
            }
          }

          if (integrationState.socialId == null) {
            console.error('Social ID is not defined for integration state', integrationState)
            state = {
              mode: 'Error',
              hint: 'Social ID is not defined',
              buttons: {
                primary: { label: ui.string.Ok, handler: close }
              }
            }
            break
          }
          void connect(integrationState.number, integrationState.socialId)
          break
        }

        case 'wantcode': {
          const number = integrationState.number

          state = {
            mode: 'WantCode',
            buttons: {
              primary: {
                label: ui.string.Next,
                handler: h(() => commandWithLoading(number, 'next', code)),
                disabled: code.match(/^\d{5}$/) == null || isLoading
              },
              secondary: { label: telegram.string.Cancel, handler: close }
            }
          }

          break
        }

        case 'wantpassword': {
          const number = integrationState.number

          state = {
            mode: 'WantPassword',
            buttons: {
              primary: {
                label: ui.string.Next,
                handler: h(() => commandWithLoading(number, 'next', password)),
                disabled: password.length === 0 || isLoading
              },
              secondary: { label: telegram.string.Cancel, handler: close }
            }
          }

          break
        }
      }
    }
  }

  async function init (): Promise<void> {
    try {
      const phoneNumber = integration?.data?.phone

      if (phoneNumber !== undefined && phoneNumber !== '') {
        phone = phoneNumber

        // If reconnecting, start authentication flow from the beginning
        if (reconnect) {
          await disconnectSession(phoneNumber)
          // Add small delay to ensure session is fully disconnected
          await new Promise((resolve) => setTimeout(resolve, 100))
          integrationState = await commandWithLoading(phoneNumber, 'start')
        } else {
          integrationState = await getState(phoneNumber)
        }
      } else {
        integrationState = 'Missing'
      }
    } catch (ex: any) {
      console.error(ex)
      state = {
        mode: 'Error',
        hint: ex.message,
        errorLabel: getErrorLabel(ex),
        buttons: {
          primary: {
            label: ui.string.Ok,
            handler: () => {
              close()
            }
          }
        }
      }
    }
  }

  async function disconnectSession (phoneNumber: string): Promise<void> {
    try {
      await disconnect(phoneNumber)
    } catch (error: any) {
      Analytics.handleError(error)
    }
  }

  void init()
</script>

<div class="card">
  <div class="flex-between header">
    <div class="overflow-label fs-title"><Label label={telegram.string.ConnectFull} /></div>
    <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
    <div
      class="tool"
      on:click={() => {
        close()
      }}
    >
      <IconClose size={'small'} />
    </div>
  </div>

  <div class="content">
    {#if state.mode === 'Loading'}
      <Label label={telegram.string.Loading} />
    {:else if state.mode === 'WantPhone'}
      <Label label={telegram.string.PhoneDescr} />
      <div class="pt-2">
        <PhoneInput label={telegram.string.Phone} placeholder={telegram.string.PhonePlaceholder} bind:value={phone} />
      </div>
    {:else if state.mode === 'WantCode'}
      <Label label={telegram.string.CodeDescr} />
      <div class="pt-2">
        <PinPad length={5} bind:value={code} bind:error />
      </div>
    {:else if state.mode === 'WantPassword'}
      <Label label={telegram.string.PasswordDescr} />

      <div class="pt-2">
        <EditBox
          label={telegram.string.Password}
          format="password"
          placeholder={telegram.string.Password}
          bind:value={password}
        />
      </div>
    {:else if state.mode === 'Authorized'}
      <Label label={telegram.string.IntegrationConnected} params={{ phone: state.hint }} />
    {:else if state.mode === 'Error'}
      <div class="flex-row-top flex-gap-1 gap-3 pt-2">
        <IconError size={'medium'} />
        {#if state.errorLabel !== undefined}
          <Label label={state.errorLabel} />
        {:else}
          <span>{state.hint}</span>
        {/if}
      </div>
    {/if}

    <div class="footer">
      {#if state.buttons?.primary}
        <Button
          label={state.buttons.primary.label}
          kind={'primary'}
          loading={isLoading}
          disabled={state.buttons.primary.disabled}
          on:click={state.buttons.primary.handler}
        />
      {/if}

      {#if state.buttons?.secondary}
        <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
        <div class="link over-underline" on:click={state.buttons.secondary.handler}>
          <Label label={state.buttons.secondary.label} />
        </div>
      {/if}
    </div>
  </div>
</div>

<style lang="scss">
  .card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 20rem;
    min-width: 20rem;
    max-width: 20rem;
    background: var(--popup-bg-color);
    border-radius: 0.75rem;
    box-shadow: var(--popup-shadow);

    .header {
      flex-shrink: 0;
      margin: 1.75rem 1.75rem 1.25rem;

      .tool {
        cursor: pointer;
        &:hover {
          color: var(--caption-color);
        }
        &:active {
          color: var(--accent-color);
        }
      }
    }

    .content {
      flex-shrink: 0;
      flex-grow: 1;
      height: fit-content;
      margin: 0 1.75rem 0.5rem;

      .footer {
        display: flex;
        flex-direction: row-reverse;
        justify-content: space-between;
        align-items: center;
        padding: 1rem 0rem;

        .link {
          color: var(--accent-color);
          &:hover {
            color: var(--caption-color);
          }
          &:active {
            color: var(--accent-color);
          }
        }
      }
    }
  }
</style>
